18. Solution: Write Your Own ViewModel Test
If you want to take a look at the solution up to this point, you can check out this Github repo. You can compare the starter code to where the code is now here.
Step 1: Compare your test to the solution
- Compare your solution versus the solution below:
TasksViewModelTest
@Test
fun setFilterAllTasks_tasksAddViewVisible() {
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
// When the filter type is ALL_TASKS
tasksViewModel.setFiltering(TasksFilterType.ALL_TASKS)
// Then the "Add task" action is visible
assertThat(tasksViewModel.tasksAddViewVisible.getOrAwaitValue(), `is`(true))
}
Notice:
- You create your
tasksViewModelusing the sameAndroidXApplicationProvider.getApplicationContext()statement - You call the
setFilteringmethod, passing in theALL_TASKSfilter type enum - You check that the
tasksAddViewVisibleis true, using thegetOrAwaitNextValuemethod
Step 2: Add a @Before rule
Notice how at the start of both of your tests, you define a TasksViewModel:
TasksViewModelTest
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
When you have repeated setup code like this shared between all tests, you can use the @Before annotation to create a setup method and remove repeated code. Since all of these tests are going to test the TasksViewModel and will need a view model, it's safe to move this code to a @Before block.
- Create a
lateinitinstance variable calledtasksViewModel - Create a method called
setupViewModel - Annotate it with
@Before - Move the view model instantiation code to
setupViewModel:
TasksViewModelTest
// Subject under test
private lateinit var tasksViewModel: TasksViewModel
@Before
fun setupViewModel() {
tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
}
- Run your code!
Warning
Do not do the following, do not initialize the tasksViewModel with its definition:
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
This will cause the same instance to be used for all tests. This is something you should avoid because each test should have a fresh instance of the subject under test (the ViewModel in this case).
Your final code for TasksViewModelTest should look like:
TasksViewModelTest
@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {
// Subject under test
private lateinit var tasksViewModel: TasksViewModel
// Executes each task synchronously using Architecture Components.
@get:Rule
var instantExecutorRule = InstantTaskExecutorRule()
@Before
fun setupViewModel() {
tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
}
@Test
fun addNewTask_setsNewTaskEvent() {
// When adding a new task
tasksViewModel.addNewTask()
// Then the new task event is triggered
val value = tasksViewModel.newTaskEvent.awaitNextValue()
assertThat(
value?.getContentIfNotHandled(), (not(nullValue()))
)
}
@Test
fun getTasksAddViewVisible() {
// When the filter type is ALL_TASKS
tasksViewModel.setFiltering(TasksFilterType.ALL_TASKS)
// Then the "Add task" action is visible
assertThat(tasksViewModel.tasksAddViewVisible.awaitNextValue(), `is`(true))
}
}